package com.youlin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.youlin.dto.BaseDataResDto;
import com.youlin.dto.BaseResDto;
import com.youlin.dto.Status;
import com.youlin.dto.req.EditUTReqDto;
import com.youlin.dto.req.FindAllTakerReqDto;
import com.youlin.dto.res.TakerLoginInnerResDto;
import com.youlin.dto.res.TakerLoginResDto;
import com.youlin.dto.res.TakerResDto;
import com.youlin.entities.*;
import com.youlin.mapper.BygUndertakerMapper;
import com.youlin.service.BygAppointmentService;
import com.youlin.service.BygDeadManService;
import com.youlin.service.BygRelationService;
import com.youlin.service.BygUndertakerService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlin.untils.*;
import org.apache.commons.lang.BooleanUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class BygUndertakerServiceImpl extends ServiceImpl<BygUndertakerMapper, BygUndertaker> implements BygUndertakerService {

    @Value("${spring.profiles.active}")
    private String active;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private BygAppointmentService bygAppointmentService;
    @Autowired
    private BygDeadManService bygDeadManService;
    @Autowired
    private BygRelationService bygRelationService;

    @Override
    public BaseDataResDto<TakerLoginResDto> takerLogin(String tel, Integer code) {
        if (active.equals("prod")) {
            Integer redisCode = RedisUtil.getObject(redisTemplate, "takerTel:" + tel, Integer.class);
            if (redisCode == null) return new BaseDataResDto<>(Status.CODEINVALID);
            if (! redisCode.equals(code)) return new BaseDataResDto<>(Status.CODEERROR);
        }

        List<BygUndertaker> undertakers = list(Wrappers.lambdaQuery(BygUndertaker.class).eq(BygUndertaker::getTel, tel));
        List<Integer> amIds = undertakers.parallelStream().map(BygUndertaker::getAmId).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(amIds)) return new BaseDataResDto<>(Status.TAKERTELERROR);
        List<BygAppointment> appointments = bygAppointmentService.list(Wrappers
                .lambdaQuery(BygAppointment.class).in(BygAppointment::getId, amIds)
                .ne(BygAppointment::getStatus, MyEnum.AM_CREATE.getValue()).orderByDesc(BygAppointment::getCreateTime));
        if (CollectionUtils.isEmpty(appointments)) return new BaseDataResDto<>(Status.TAKERTELERROR);

        List<TakerLoginInnerResDto> innerResDtos = BaseService.dealWithOneToOne(appointments,
                BygAppointment::getId, TakerLoginInnerResDto.class, undertakers, BygUndertaker::getAmId);

        innerResDtos = BaseService.findAndDealWithOneToOne(innerResDtos, TakerLoginInnerResDto::getDmId,
                TakerLoginInnerResDto.class, bygDeadManService, BygDeadMan::getId);

        innerResDtos = BaseService.findAndDealWithOneToOne(innerResDtos, TakerLoginInnerResDto::getRelationId,
                TakerLoginInnerResDto.class, bygRelationService, BygRelation::getId);

        String takerToken = Md5Util.getUUID();
        RedisUtil.setObjectExpire(redisTemplate, "takerToken:" + takerToken, tel);

        TakerLoginResDto takerLoginResDto = new TakerLoginResDto().setToken(takerToken).setAmList(innerResDtos);
        return new BaseDataResDto<TakerLoginResDto>(Status.SUCCESS).setData(takerLoginResDto);
    }

    @Override
    public BaseResDto<Status, String> sendCode(String tel) {
        if (! active.equals("prod")) return new BaseResDto<>(Status.SUCCESS);

        // 防止表单重复提交
        if (! redisTemplate.opsForValue().setIfAbsent("sendCodeLock2:"+tel, 123))
            return new BaseResDto<>(Status.SENDCODEERROR);
        try {
            LambdaQueryWrapper<BygUndertaker> wrapper = Wrappers
                    .lambdaQuery(BygUndertaker.class).eq(BygUndertaker::getTel, tel);
            if (count(wrapper) == 0) return new BaseResDto<>(Status.TAKERTELERROR);
            Integer code = smsUtil.sendSms(tel, SMSUtil.Template.TAKERLOGIN);

            // 将验证码缓存限时缓存入Redis
            RedisUtil.setObjectExpire(redisTemplate, "takerTel:"+tel, code, 5, TimeUnit.MINUTES);
        } catch (SmsLimitExpectation e) {
            return new BaseResDto<>(Status.SENDCODEERROR);
        } catch (SmsErrorExpectation e) {
            return new BaseResDto<>(Status.SMSERROR);
        } finally {
            // 定时解锁Redis
            redisTemplate.expire("sendCodeLock2:"+tel, 60, TimeUnit.SECONDS);
        }
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    @Transactional
    public BaseResDto<Status, String> editUnderTaker(Integer amId, Integer dmId, List<EditUTReqDto> takers) {
        if (takers.parallelStream().anyMatch(taker -> MyBeanUtil.isParamError(taker)))
            return new BaseResDto<>(Status.ERROR);

        List<BygUndertaker> undertakers = takers.parallelStream()
                .map(taker -> MyBeanUtil.copyProperties(taker, BygUndertaker.class).setAmId(amId).setDmId(dmId))
                .collect(Collectors.toList());
        List<BygUndertaker> addTakers = undertakers.parallelStream()
                .filter(taker -> BooleanUtils.isFalse(taker.getIsDel()))
                .filter(taker -> taker.getId() == null)
                .map(taker -> taker.setCreateTime(LocalDateTime.now()))
                .collect(Collectors.toList());
        List<BygUndertaker> updTakers = undertakers.parallelStream()
                .filter(taker -> BooleanUtils.isFalse(taker.getIsDel()))
                .filter(taker -> taker.getId() != null)
                .map(taker -> taker.setUpdateTime(LocalDateTime.now()))
                .collect(Collectors.toList());

        Set<String> unDeltels = takers.parallelStream().filter(taker -> BooleanUtils.isFalse(taker.getIsDel()))
                .collect(Collectors.mapping(EditUTReqDto::getTel, Collectors.toSet()));
        if (unDeltels.size() < addTakers.size() + updTakers.size()) return new BaseResDto<>(Status.TELEDUPLICATE);

        List<BygUndertaker> editTakers = Lists.newArrayList();
        editTakers.addAll(addTakers);
        editTakers.addAll(updTakers);
        if (! saveOrUpdateBatch(editTakers)) return new BaseResDto<>(Status.ERROR);

        List<Integer> delIds = undertakers.parallelStream().filter(BygUndertaker::getIsDel)
                .map(BygUndertaker::getId).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(delIds)  && !removeByIds(delIds))
            return new BaseResDto<>(Status.ERROR);

        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    public BaseDataResDto<List<TakerResDto>> findAllUnderTakers(FindAllTakerReqDto data) {
        LambdaQueryWrapper<BygUndertaker> wrapper = Wrappers.lambdaQuery(BygUndertaker.class)
                .eq(BygUndertaker::getAmId, data.getAmId()).orderByDesc(BygUndertaker::getCreateTime);
        if (StringUtils.isNotBlank(data.getName())) wrapper.like(BygUndertaker::getName, data.getName());
        if (StringUtils.isNotBlank(data.getTel())) wrapper.like(BygUndertaker::getTel, data.getTel());

        List<BygUndertaker> undertakers = list(wrapper);
        if (CollectionUtils.isNotEmpty(undertakers)) {
            List<TakerResDto> takerResDtos = BaseService.findAndDealWithOneToOne(undertakers, BygUndertaker::getRelationId,
                    TakerResDto.class, bygRelationService, BygRelation::getId);
            return new BaseDataResDto<List<TakerResDto>>(Status.SUCCESS).setData(takerResDtos);
        } else return new BaseDataResDto<List<TakerResDto>>(Status.SUCCESS).setData(Lists.newArrayList());
    }

    @Override
    public BaseResDto<Status, String> delTaker(Integer data) {
        if (! removeById(data)) return new BaseResDto<>(Status.ERROR);
        return new BaseResDto<>(Status.SUCCESS);
    }

}
